﻿using Dyao.Book.Contract.Serialization;
using Dyao.Book.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Reflection;

namespace Dyao.Book.Web.Extensions
{
    public class ExtensionDataContractResolver : DefaultContractResolver
    {
        private static readonly string _defaultDescriptionNameFormat = "{0}Desc";
        private readonly ExtensionDataContractResolverOptions _options;
        public ExtensionDataContractResolver(ExtensionDataContractResolverOptions options)
        {
            this._options = options;
            if (_options.UseCamelCasing)
            {
                this.NamingStrategy = new CamelCaseNamingStrategy()
                {
                    ProcessDictionaryKeys = _options.ProcessDictionaryKeys,
                };
            }
        }

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var jpList = base.CreateProperties(type, memberSerialization);
            if (memberSerialization == MemberSerialization.OptIn) return jpList;

            var list = new List<JsonProperty>();

            foreach (var jp in jpList)
            {
                list.Add(jp);

                var underlyingProertyType = UnwrapNullableType(jp.PropertyType);
                JsonProperty property = null;
                if (underlyingProertyType.IsEnum)
                {
                    property = CreateEnumDescriptionJsonProperty(jp);
                }
                else if (underlyingProertyType == typeof(bool))
                {
                    property = CreateBoolDescriptionJsonProperty(jp);
                }
                else if (underlyingProertyType == typeof(string))
                {
                    property = CreateStringEnumDescriptionJsonProperty(jp);
                }

                if (property != null)
                {
                    list.Add(property);
                }
            }

            return list;
        }

        #region create custom jsonProperty

        protected JsonProperty CreateEnumDescriptionJsonProperty(JsonProperty jsonProperty)
        {
            if (!jsonProperty.Readable) return null;

            if ((!TryGetAttribute(jsonProperty.AttributeProvider?.GetAttributes(true), out ShowEnumDescriptionAttribute enumDescriptionAttribute)) && _options.IsAddEnumDescription)
            {
                enumDescriptionAttribute = new ShowEnumDescriptionAttribute()
                {
                    NameFormat = _options.EnumDescriptionNameFormat,
                };
            }

            if (enumDescriptionAttribute == null || !enumDescriptionAttribute.FormatEnabled)
            {
                return null;
            }

            var valueFormatter = new EnumDescriptionValueFormatter();
            var nameFormat = GetOrDefault(enumDescriptionAttribute.NameFormat, _defaultDescriptionNameFormat);
            var propertyName = string.Format(nameFormat, jsonProperty.PropertyName);
            var valueProvider = new AddtionDataValueProvider(valueFormatter, jsonProperty.ValueProvider);

            var property = CreateAddtionDataProperty(jsonProperty, propertyName, typeof(string), valueProvider);

            return property;
        }

        protected JsonProperty CreateBoolDescriptionJsonProperty(JsonProperty jsonProperty)
        {
            if (!jsonProperty.Readable) return null;

            if (!TryGetAttribute(jsonProperty.AttributeProvider?.GetAttributes(true), out ShowBoolDescriptionAttribute boolAttribute) && _options.IsAddBoolDescription)
            {
                boolAttribute = new ShowBoolDescriptionAttribute()
                {
                    NameFormat = _options.BoolDescriptionNameFormat,
                };
            }

            if (boolAttribute == null || !boolAttribute.FormatEnabled)
            {
                return null;
            }

            var valueFormatter = new BoolDescriptionValueFormatter(boolAttribute.TrueText, boolAttribute.FalseText);
            var nameFormat = GetOrDefault(boolAttribute.NameFormat, _defaultDescriptionNameFormat);
            var propertyName = string.Format(nameFormat, jsonProperty.PropertyName);
            var valueProvider = new AddtionDataValueProvider(valueFormatter, jsonProperty.ValueProvider);

            var property = CreateAddtionDataProperty(jsonProperty, propertyName, typeof(string), valueProvider);

            return property;
        }

        protected JsonProperty CreateStringEnumDescriptionJsonProperty(JsonProperty jsonProperty)
        {
            if (!jsonProperty.Readable) return null;

            TryGetAttribute(jsonProperty.AttributeProvider?.GetAttributes(true), out ShowStringEnumDescriptionAttribute attr);

            if (attr == null || !attr.FormatEnabled)
            {
                return null;
            }

            var valueFormatter = new EnumStringsDescriptionValueFormatter(attr.EnumType, attr.Separator, attr.DescSeparator, attr.IsIgnoreCase);
            var nameFormat = GetOrDefault(attr.NameFormat, _defaultDescriptionNameFormat);
            var propertyName = string.Format(nameFormat, jsonProperty.PropertyName);
            var valueProvider = new AddtionDataValueProvider(valueFormatter, jsonProperty.ValueProvider);

            var property = CreateAddtionDataProperty(jsonProperty, propertyName, typeof(string), valueProvider);

            return property;
        }

        protected JsonProperty CreateAddtionDataProperty(JsonProperty jsonProperty, string propertyName, Type propertyType, IValueProvider valueProvider)
        {
            var property = new JsonProperty();
            property.PropertyName = this.NamingStrategy.GetPropertyName(propertyName, false);
            property.UnderlyingName = property.PropertyName;
            property.PropertyType = propertyType;
            property.DeclaringType = jsonProperty.DeclaringType;
            property.ShouldSerialize = jsonProperty.ShouldSerialize;
            property.ValueProvider = valueProvider;
            property.Readable = true;

            //property.AttributeProvider = null;
            //property.NullValueHandling = null;
            //property.ReferenceLoopHandling = null;
            //property.ObjectCreationHandling = null;
            //property.TypeNameHandling = null;
            //property.IsReference = null;
            //property.ItemIsReference = null;
            //property.ItemConverter = null;
            //property.ItemReferenceLoopHandling = null;
            //property.ItemTypeNameHandling = null;
            //property.DefaultValueHandling = null;

            return property;
        }

        #endregion //end custom jsonProperty

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var jsonProperty = base.CreateProperty(member, memberSerialization);
            CreateValueProvider(member, memberSerialization, jsonProperty);
            CreateConverter(member, jsonProperty);

            return jsonProperty;
        }

        protected virtual void CreateValueProvider(MemberInfo member, MemberSerialization memberSerialization, JsonProperty jsonProperty)
        {
            var propertyType = jsonProperty.PropertyType;
            var attributes = jsonProperty.AttributeProvider.GetAttributes(true);

            var hasEmptyAttr = TryGetAttribute(attributes, out ConvertNullToEmptyAttribute emptyAttribute);

            if (((hasEmptyAttr && emptyAttribute.IsConvetNullToEmpty) || (!hasEmptyAttr && _options.IsConvertNullStringToEmpty))
                && propertyType == typeof(string))
            {
                jsonProperty.ValueProvider = new StringValueProvider(member, true);
            }
            else if (((hasEmptyAttr && emptyAttribute.IsConvetNullToEmpty) || (!hasEmptyAttr && _options.IsConvetNullListToEmpty))
                && (propertyType.IsArray || propertyType.IsGenericType))
            {
                Type itemType = null;
                if (jsonProperty.PropertyType.IsArray && propertyType.GetArrayRank() == 1)//一维数组
                {
                    var gType = propertyType.GetInterfaces().FirstOrDefault(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(IList<>));
                    if (gType != null && gType.GenericTypeArguments.Length > 0)
                    {
                        itemType = gType.GenericTypeArguments[0];
                    }
                }
                else if (propertyType.IsGenericType
                    && (propertyType.GetGenericTypeDefinition() == typeof(IList<>) || propertyType.GetGenericTypeDefinition() == typeof(List<>)))
                {
                    itemType = propertyType.GenericTypeArguments[0];
                }

                if (itemType != null)
                {
                    var valueProviderType = typeof(ListValueProvider<>).MakeGenericType(itemType);
                    var valueProvider = Activator.CreateInstance(valueProviderType, new object[] { member, true });
                    jsonProperty.ValueProvider = (IValueProvider)valueProvider;
                }
            }
        }

        protected void CreateConverter(MemberInfo _,  JsonProperty jsonProperty)
        {
            var converter = jsonProperty.Converter;
            if (converter != null) return;

            var attributes = jsonProperty.AttributeProvider.GetAttributes(true);
            var underlyingProertyType = UnwrapNullableType(jsonProperty.PropertyType);

            if (underlyingProertyType.IsEnum)
            {
                if (TryGetAttribute<StringEnumAttribute>(attributes, out var stringEnumAttribute) && stringEnumAttribute.IsStringValue)
                {
                    converter = new StringEnumConverter();
                }
            }
            else if (underlyingProertyType == typeof(DateTime))
            {

                if (TryGetAttribute(attributes, out DatetimeFormatAttribute datetimeAttriubte) && datetimeAttriubte.FormatEnabled)
                {
                    converter = new IsoDateTimeConverter
                    {
                        DateTimeFormat = datetimeAttriubte.Format,
                        Culture = datetimeAttriubte.Culture
                    };
                }
            }

            if (converter != null)
            {
                jsonProperty.Converter = converter;
            }
        }


        protected static Type UnwrapNullableType(Type type)
           => Nullable.GetUnderlyingType(type) ?? type;

        protected static string GetOrDefault(string value, string defaultVale)
        {
            return string.IsNullOrWhiteSpace(value) ? defaultVale : value;
        }

        private static bool TryGetAttribute<T>(IList<Attribute> attributes, out T attr)
            where T : Attribute
        {
            attr = (T)attributes?.FirstOrDefault(a => a is T);
            return attr != null;
        }
    }

    public class AddtionDataValueProvider : IValueProvider
    {
        private readonly IAdditionDataValueFormatter _addtionDataValueFormatter;
        private readonly IValueProvider _valueProvider;

        public AddtionDataValueProvider(IAdditionDataValueFormatter addtionDataValueFormatter, IValueProvider valueProvider)
        {
            this._addtionDataValueFormatter = addtionDataValueFormatter;
            this._valueProvider = valueProvider;
        }
        public object GetValue(object target)
        {
            var sourceValue = _valueProvider.GetValue(target);
            var value = _addtionDataValueFormatter.Format(sourceValue);
            return value;
        }

        public void SetValue(object target, object value)
        {
            throw new NotImplementedException();
        }
    }
}
